﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using SimpleOrmCFPC.Core.Query.OrderBys;
using SimpleOrmCFPC.Core.Query.Requests;
using SimpleOrmCFPC.Core.Query.Select;
using SimpleOrmCFPC.Core.Query.expressions;
using SimpleOrmCFPC.Core.Query.expressions.ExprssionCompilers;
using SimpleOrmCFPC.Core.Query.expressions.ExprssionCompilers.Helpers;

namespace SimpleOrmCFPC.Core.Query
{
    public class SQLLinq<T> : IEnumerable<T>
    {
        private OrderByComputer orderByComputer;
        private SQLRequest request;
        private RequestExecutor requestxecutor;
        private SelectComputer selectComputer;
        private AnyWhereElementFromExpressionComputer expressionCompiler;

        public SQLLinq(OrderByComputer orderByComputer, RequestExecutor requestxecutor,
                       SelectComputer selectComputer, SQLRequest request, AnyWhereElementFromExpressionComputer expressionCompiler)
        {
            this.orderByComputer = orderByComputer;
            this.requestxecutor = requestxecutor;
            this.selectComputer = selectComputer;
            this.request = request;
            this.expressionCompiler = expressionCompiler;
        }

        public IEnumerator<T> GetEnumerator()
        {
            
            return requestxecutor.Get<T>(request).GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public SQLLinq<T> Take(int n)
        {
            SQLLinq<T> q = CloneSub<T>();
            q.request._limit = n;
            return q;
        }

        public SQLLinq<T> Skip(int n)
        {
            SQLLinq<T> q = CloneSub<T>();
            q.request._offset = n;
            return q;
        }

        public SQLLinq<U> Select<U>(Expression<Func<T, U>> proj)
        {
            SQLLinq<U> q = CloneSub<U>();
            q.request._selects.AddRange(selectComputer.ComputeSelects(proj));
            return q;
        }

        public SQLLinq<T> OrderBy<U>(Expression<Func<T, U>> orderExpr)
        {
            return AddOrderBy(orderExpr, true);
        }

        public SQLLinq<T> OrderByDescending<U>(Expression<Func<T, U>> orderExpr)
        {
            return AddOrderBy(orderExpr, false);
        }

        private SQLLinq<T> AddOrderBy<U>(Expression<Func<T, U>> orderExpr, bool asc)
        {
            SQLLinq<T> q = CloneSub<T>();
            q.request._orderBys.Add(orderByComputer.ComputeOrderBy(orderExpr, asc));
            return q;
        }


        public SQLLinq<T> Where(Expression<Func<T, bool>> predExpr)
        {
            if (predExpr.NodeType == ExpressionType.Lambda)
            {
                SQLLinq<T> q = CloneSub<T>();
                q.AddWhere(predExpr.Body);
                return q;
            }
            throw new NotSupportedException("Must be a predicate");
        }

        private void AddWhere(Expression pred)
        {
            request._where.Add(expressionCompiler.Compute(pred));
            expressionCompiler.FillAdditionalInformation(pred, request);
        }

        private SQLLinq<U> CloneSub<U>()
        {
            return new SQLLinq<U>(orderByComputer, requestxecutor, selectComputer, new SQLRequest(request._limit, request._offset, request._orderBys, request._where, request.tableNames, request._selects), expressionCompiler);
        }
    }
}